
#include "sipcall.h"
#include <cassert>
#include <cstring>
#include <cstdlib>
#include <fstream>
#include <sstream>
using namespace std;
#include "eXosip2/eXosip.h"

SipCall::SipCall(eXosip_t* ctx, const ConfigParam& param)
    : mParam(param)
    , mSipCtx(ctx)
    , mCallState(CALL_STATUS_NONE)
    , mDID(0)
    , mCID(0)
    , mNatAddress(NULL)
    , mNatPort(NULL)
{
}

SipCall::~SipCall()
{
    if (mNatAddress) delete[] mNatAddress;
    if (mNatPort) delete[] mNatPort;
}

void SipCall::updateNatInfo(const std::string& nataddress, int natport)
{
    updateNatAddress(nataddress);
    updateNatPort(natport);
}

void SipCall::updateNatAddress(const std::string& nataddress)
{
    if (mNatAddress)
    {
        delete[] mNatAddress;
    }
    mNatAddress = new char[nataddress.length() + 1];
    strcpy(mNatAddress, nataddress.c_str());
}

void SipCall::updateNatPort(int natport)
{
    if (!mNatPort)
    {
        mNatPort = new char[6];
    }

    sprintf(mNatPort, "%d", natport);
}

bool SipCall::sendOffer(const std::string& targetid, const std::string& sdp)
{
    if (isIncall())
    {
        Console(INVITE, << "There is a call in processing...");
        return false;
    }

    //modify call request line
    ExosipAutoLock locker(mSipCtx);
    osip_message_t* invite;
    int ret = eXosip_call_build_initial_invite(mSipCtx, &invite, mParam.get_to(targetid).c_str(), mParam.get_from().c_str(), mParam.get_route().c_str(), "test call");
    ret = osip_message_set_content_type(invite, "application/sdp");
    ret = osip_message_set_body(invite, sdp.c_str(), sdp.size());
    mCID = eXosip_call_send_initial_invite(mSipCtx, invite);

    Console(INVITE, << "send invite request (cid:" << mCID << ")");

    transitionState(CALL_STATUS_CALLING);

    return true;
}

bool SipCall::sendAck(int tid)
{
    int ret = OSIP_SUCCESS;
    osip_message_t* ack;
    ret = eXosip_call_build_ack(mSipCtx, tid, &ack);
    ret = eXosip_call_send_ack(mSipCtx, tid, ack);
    return true;
}

void SipCall::onOffer(int did, int cid, int tid)
{
    mDID = did;
    mCID = cid;
    transitionState(CALL_STATUS_INCOMING);

    ifstream ifs("sdp-answer.txt");
    if (!ifs)
    {
        Console(INVITE, << "not exist sdp.txt");
        terminate();
        return;
    }

    stringstream ss;
    ss << ifs.rdbuf();
    string sdp(ss.str());
    ifs.close();

    sendCallResponse(tid, 200, "application/sdp", sdp);
}

void SipCall::on100()
{
    //Console(INVITE, << "received invite response 100Trying from cid:" << mCID);
    transitionState(CALL_STATUS_100Tring);
}

void SipCall::on180()
{
    //Console(INVITE, << "received invite response 180Ring from cid:" << mCID);
    transitionState(CALL_STATUS_180Ring);
}

void SipCall::onAnswer(int did, int cid, int tid)
{
    //Console(INVITE, << "received invite response 200OK from cid:" << mCID);
    transitionState(CALL_STATUS_200OK);
    mDID = did;
    mCID = cid;
    sendAck(tid);
    transitionState(CALL_STATUS_INCALL);
}

void SipCall::onFail(int tid, int statuscode)
{
    Console(INVITE, << "received invite failure with status code:" << statuscode);
    sendAck(tid);
    transitionState(CALL_STATUS_FAIL);
    //////////////////////////////////////////////////////////////////////////
    ///terminate();
}

void SipCall::onAck()
{
    transitionState(CALL_STATUS_INCALL);
}

void SipCall::terminate()
{
    if (isIncall())
    {
        int ret = eXosip_call_terminate(mSipCtx, mCID, mDID);
        Console(INVITE, << "terminate call with did:" << mDID << ", cid:" << mCID);

        transitionState(CALL_STATUS_HANGUP);
    }
}


bool SipCall::sendCallRequest(const std::string& method, const std::string& contenttype, const std::string& msg, void* appdata /*= NULL*/)
{
    int ret = OSIP_SUCCESS;
    osip_message_t* request = NULL;

    ExosipAutoLock locker(mSipCtx);
    ret = eXosip_call_build_request(mSipCtx, mDID, method.c_str(), &request);
    if (OSIP_SUCCESS != ret)
    {
        Console(INVITE, << "eXosip_call_build_request failed with did:" << mDID << ", errcode:" << ret);
        return false;
    }
    if (msg.size() > 0)
    {
        ret = osip_message_set_content_type(request, contenttype.c_str());
        ret = osip_message_set_body(request, msg.c_str(), msg.size());
        request->application_data = appdata;
    }

    ret = eXosip_call_send_request(mSipCtx, mDID, request);
    if (OSIP_SUCCESS == ret)
    {
        Console(INVITE, << "send in-dialog message by:" << (int)appdata << " with content type:" << contenttype << ", body:" << msg);
        //         Console(INFO, << "send call request to did:" << did << ", method:" << method 
        //             << ", content type:" << contenttype << ", content len:" << msg.size() << ", body:" << endl << msg);
        return true;
    }
    else
    {
        Console(INVITE, << "eXosip_call_send_request failed with did:" << mDID << ", errcode:" << ret);
        return false;
    }
}

bool SipCall::sendCallResponse(int tid, int statuscode, const std::string& contenttype, const std::string& msg)
{
    int ret = OSIP_SUCCESS;
    osip_message_t* answer = NULL;

    ExosipAutoLock locker(mSipCtx);
    ret = eXosip_call_build_answer(mSipCtx, tid, statuscode, &answer);
    if (OSIP_SUCCESS != ret)
    {
        Console(INVITE, << "failed to build call answer to tid:" << tid);
        return false;
    }
    if (msg.size() > 0)
    {
        ret = osip_message_set_content_type(answer, contenttype.c_str());
        ret = osip_message_set_body(answer, msg.c_str(), msg.size());
    }

    //revise contact
    osip_contact_t* contact;
    ret = osip_message_get_contact(answer, 0, &contact);
    if (contact)
    {
        contact->url->host = mNatAddress;
        contact->url->port = mNatPort;
    }

    ret = eXosip_call_send_answer(mSipCtx, tid, statuscode, answer);
    if (OSIP_SUCCESS == ret)
    {
        Console(INVITE, << "send call answer to tid:" << tid << ", statuscode:" << statuscode << ", content type:" << contenttype
            << ", content len:" << msg.size() << ", content body:" << endl << msg);
        return true;
    }
    else
    {
        Console(INVITE, << "Failed to send call answer to tid:" << tid);
        return false;
    }
}

std::string SipCall::toString(State state)
{
    switch (state)
    {
    case CALL_STATUS_NONE:
        return "idle";
    case CALL_STATUS_CALLING:
        return "calling...";
    case  CALL_STATUS_INCOMING:
        return "incoming...";
    case CALL_STATUS_100Tring:
        return "100-Trying...";
    case CALL_STATUS_180Ring:
        return "180-Ringing...";
    case CALL_STATUS_200OK:
        return "200-OK";
    case CALL_STATUS_FAIL:
        return "failed";
    case CALL_STATUS_INCALL:
        return "in-call";
    case CALL_STATUS_HANGUP:
        return "hang-up...";
    case CALL_STATUS_COMPLETE:
        return "complete";
    }
    assert(false);
    return "Undefined";
}

void SipCall::transitionState(SipCall::State s)
{
    Console(INVITE, << "Invite stat changed: " << toString(mCallState) << " ---> " << toString(s));
    mCallState = s;
}

bool SipCall::isIncall() const
{
    return ((mCallState != CALL_STATUS_NONE) &&
        (mCallState != CALL_STATUS_COMPLETE) &&
        (mCallState != CALL_STATUS_FAIL));
}
